package MonteCarlo;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;



public class MonteCarlo implements Callable<Double>{
	
	/** The Monte Carlo Algorithm method
	 * 
	 * @param callPutFlag	// Specify if option is call or a put
	 * @param S				// Actual price is underlying
	 * @param X				// Option strike price 
	 * @param T				// Time to option maturity (year)
	 * @param r				// Interest rate without risks
	 * @param b				// Option holding interest rate 
	 * @param v				// Underlying price volatility 
	 * @param nSteps		// The number of steps to execute for each option price path
	 * @param nSimulations	// The number of simulations to run 
	 * @return
	 */
	
	private String callPutFlag;
	private double s;
	private double x;
	private double t;
	private double r;
	private double b;
	private double v;
	private int nSteps;
	private int nSimulations;
	private int poolSize;
	private double result;
	private double sum;

	

	public MonteCarlo(String cPF, double dS, double dX, double dT, double dR, double dB, double dV, int nSt, int nSi, int nbT){
		this.callPutFlag = cPF;
		this.s = dS;
		this.x = dX;
		this.t = dT;
		this.r = dR;
		this.b = dB;
		this.v = dV;
		this.nSteps = nSt;
		this.nSimulations = nSi;
		this.poolSize = nbT;
	}
	
	public Double call() throws Exception {
		

		double dt=0;
		final double drift;
		final double vSqrdt;
		int i;
		int z=0; 
		final int zTmp;
		final double st = s;
		//final Random random= new Random();
        
		dt = t / nSteps; 
		
		drift = (b - Math.pow(v,2)/ 2) * dt;
		vSqrdt = v * Math.sqrt(dt);
		
		if (callPutFlag.equals("c")){
			z = 1;
		}
		else if(callPutFlag.equals("p")){
			z = -1 ;
		}
			
		//Recuperation du temps de depart;
		long timeStart = System.currentTimeMillis();

        final List<Callable<Double>> listCallable = new ArrayList<Callable<Double>>();
        zTmp = z;
        

        
        for (i=0; i< nSimulations; i++){
        	listCallable.add(new Callable<Double>() {

				@Override
				public Double call() throws Exception {
					Double stBis = st;
					Random random= new Random();
					for (int j=0; j< nSteps; j++){
						stBis = stBis * Math.exp(drift + (vSqrdt * random.nextGaussian()));
						//System.out.println(st);
					}

			        Double result = Math.max(zTmp * (stBis - x), 0);
			        
					//System.out.println("Thread ID = " + Thread.currentThread().getId());
			        //System.out.println("Name = " + Thread.currentThread().getName());
			        //System.out.println("Result threads intermediare = " + result);
					return result;
				}
			});
        }
        
		ExecutorService executorPool = Executors.newFixedThreadPool(poolSize);
        List<Future<Double>> list = null;
        
        //Execution de la pile de threads
        list = executorPool.invokeAll(listCallable, 1000, TimeUnit.SECONDS);
        
        //Addition des resultats obtenu dans chaque threads
        for(final Future<Double> tmp : list){
        	sum+=tmp.get();
        }
	    executorPool.shutdown();
	    
		result = Math.exp(-r * t) * (sum / nSimulations);
		
		//Recuperation du temps d'arrivee
		long timeEnd = System.currentTimeMillis();
		
		//Affichage temps de depart et temps d'arrivee
		float  time = (timeEnd- timeStart)/1000;
		
        System.out.println("Nombre de Threads dans un MonteCarlo = " + poolSize);
		System.out.println("Total time du multi threading sur les simulations : " + time + "s");
		
		//System.out.println("Thread ID = " + Thread.currentThread().getId());
        //System.out.println("Name = " + Thread.currentThread().getName());
		
		System.out.println("Result Algorithme MonteCarlo = "+ result);
		
		return result;
	}
}
